﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using X3DXNA.X3DSchema;
using X3DXNA.utils;
using X3DXNA.services;

namespace X3DXNA.core.Controllers
{
    public class ViewPointController
    {

        #region CONSTANTS

        long animationTime = TimeSpan.FromSeconds(1.0).Ticks;

        #endregion

        #region Camera Struct

        public struct MyCamera
        {
            public String DEF;
            public String description;
            public Vector3 position;
            public Vector4 orientation;
            public float fieldOfView;
            public bool jump;
            public Vector3 centerOfRotation;
            public bool set_bind;
        }

        #endregion

        #region Private Fields

        List<MyCamera> cameras;
        myBasicEffect basicEffect;
        Camera camera;
        int numberOfCameras, actualCamera;
        int getNextCameraIndex { get { return ((actualCamera + 1) % numberOfCameras); } }
        int getPrevCameraIndex { get { if (actualCamera == 0) return numberOfCameras - 1; else return ((actualCamera - 1) % numberOfCameras); } }
        bool isAnimating;
        long animationStartsTime;
        Vector3 animationStartPosition;
        Quaternion animationStartOrientation;
        Vector3 animationEndPosition;
        Quaternion animationEndOrientation;

        #endregion

        #region Constructor

        public ViewPointController(Game game)
        {
            //Recupero il basicEffect
            basicEffect = (myBasicEffect)game.Services.GetService(typeof(myBasicEffect));
            //Inizializzo lista di camere
            cameras = new List<MyCamera>();
            //Creo la camera con i parametri di default. Sarà utile nel qual caso non ci saranno ViewPoint nel codice X3D.
            camera = new Camera(game, new Vector3(0, 0, 10), new Vector4(0, 0, 1, 0), 0.7854f);
        }

        #endregion

        #region Update

        public void Update(GameTime gameTime)
        {
            camera.HandleDefaultKeyboardControls(Keyboard.GetState(), gameTime);
            UpdateAnimation();
            updateCamera();
        }

        #endregion

        #region Private Methods

        private void UpdateAnimation()
        {
            if (isAnimating)
            {
                long now = DateTime.Now.Ticks;
                if (now > animationStartsTime + animationTime)
                    isAnimating = false;
                else
                {
                    float value = (float)(now - animationStartsTime) / animationTime;
                    camera.setPositionAndQuaternionOrientation(
                        Vector3.Lerp(animationStartPosition, animationEndPosition, value),
                        Quaternion.Lerp(animationStartOrientation, animationEndOrientation, value)
                        );
                }
            }
        }

        private void startAnimation(Vector3 startPosition, Quaternion startOrientation, Vector3 endPosition, Quaternion endOrientation)
        {
            isAnimating = true;
            animationStartsTime = DateTime.Now.Ticks;
            animationStartPosition = startPosition;
            animationStartOrientation = startOrientation;
            animationEndPosition = endPosition;
            animationEndOrientation = endOrientation;
        }

        /// <summary>
        /// Funzione che aggiorna il BasicEffect con le informazioni presenti nella camera.
        /// </summary>
        private void updateCamera()
        {
            basicEffect.view = camera.viewMatrix;
            basicEffect.projection = camera.projectionMatrix;
        }

        /// <summary>
        /// Passa ad un ViewPoint diverso, passato come argomento.
        /// </summary>
        /// <param name="c">Il ViewPoint da settare</param>
        private void switchCamera(MyCamera c)
        {
            camera.setPositionAxisOrientationAndFieldOfView(c.position, c.orientation, c.fieldOfView);

            updateCamera();
#warning Da completare con altre informazioni sulla camera
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Ottiene la Matrice View dell'attuale camera.
        /// </summary>
        /// <returns>ViewMatrix</returns>
        public Matrix getActualView()
        {
            return camera.viewMatrix;
        }

        /// <summary>
        /// Ottiene la Matrice Projection dell'attuale camera.
        /// </summary>
        /// <returns>ProjectionMatrix</returns>
        public Matrix getActualProjection()
        {
            return camera.projectionMatrix;
        }

        /// <summary>
        /// Passa al successivo elemento ViewPoint nella lista.
        /// </summary>
        public void nextCamera()
        {
            if (numberOfCameras > 0)
            {
                goToCamera(cameras[getNextCameraIndex]);
                actualCamera = getNextCameraIndex;
            }
        }

        /// <summary>
        /// Passa al precedente elemento ViewPoint nella lista.
        /// </summary>
        public void prevCamera()
        {
            if (numberOfCameras > 0)
            {
                goToCamera(cameras[getPrevCameraIndex]);
                actualCamera = getPrevCameraIndex;
            }
        }

        /// <summary>
        /// Esegue un animazione verso la camera passata come parametro.
        /// </summary>
        public void goToCamera(MyCamera cameraToGo)
        {
            startAnimation(camera.position, camera.quaternionOrientation, cameraToGo.position, utility.vector4ToQuaternionRotation(cameraToGo.orientation));
        }

        /// <summary>
        /// Controlla se ci sono dei ViewPoint salvati in lista, e in caso affermativo setta come vista principale il 
        /// primo che trova. Da lanciare dopo aver terminato l'analisi del codice X3D.
        /// </summary>
        public void initializeViewPointController()
        {
            if (cameras.Count != 0)
                switchCamera(cameras[0]);
            else
                updateCamera();
        }

        public void addViewPoint(Viewpoint v)
        {
            MyCamera newCamera = new MyCamera();

            if (v.DEF != null)
                newCamera.DEF = v.DEF;
            if (v.description != null)
                newCamera.description = v.description;
            if (v.position != null)
                newCamera.position = utility.stringToVector3(v.position);
            if (v.orientation != null)
            {
                newCamera.orientation = utility.stringToVector4(v.orientation);
                newCamera.orientation.W = -newCamera.orientation.W;
            }
            if (v.fieldOfView != null)
                newCamera.fieldOfView = utility.stringToFloat(v.fieldOfView);
            if (v.jump != null)
                newCamera.jump = utility.stringToBool(v.jump);
            if (v.centerOfRotation != null)
                newCamera.centerOfRotation = utility.stringToVector3(v.centerOfRotation);
            if (v.set_bind != null)
                newCamera.set_bind = utility.stringToBool(v.set_bind);

            cameras.Add(newCamera);
            numberOfCameras++;
        }

        #endregion

    }
}
